internal/as_user: drop supplementary groups - #2301
Conversation
set_eids() switched the effective gid and uid to the target user but never touched the supplementary group list, so the privilege-dropped thread kept the caller's group memberships while acting as that user. A directory reachable only through one of root's supplementary groups stayed reachable for the duration of the switch. Drop the list with setgroups(0, NULL) before relinquishing the gid and uid, while the thread still holds the privilege required to make that call, following the revocation order described in CERT POS36-C. Fixes coreos#2242 Signed-off-by: Jason Colapietro <jasoncola1@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review. 📝 WalkthroughWalkthrough
ChangesPrivilege-drop hardening
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The change clears supplementary groups before relinquishing user and group privileges, preventing unintended retained access while acting as another user. It is localized and tested; no actionable merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 unsupported.) Full details: Commit Message ConventionExplanation The PR contains one non-merge commit: ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Binary size report (
|
| Size | |
|---|---|
Base (main) |
33MiB |
| PR (#2301) | 33MiB |
| Delta | +16B (0.00%) |
Summary
set_eids()ininternal/as_user/as_user.cswitches the effective gid and uidto the target user, but never touches the supplementary group list. The
privilege-dropped thread therefore keeps the caller's group memberships — in
practice root's — while acting as that user.
This drops the list with
setgroups(0, NULL)before the gid and uid arerelinquished, while the thread still holds the privilege required to make that
call, following the revocation order described in
CERT POS36-C.
Reproducer
I built a probe against
as_user.cbefore and after the change. It makes thecaller (root) a member of gid 4242, creates a directory owned
root:4242withmode
0770— so "other" has no access — and then asksau_open()to create afile inside it as
uid=65534 gid=65534, which is not a member of 4242:au_opensucceeded (fd=3): the thread kept root's membership of gid 4242au_openfailed withEACCES, as it shouldSo the retained groups do grant real access during the switch, not just a
theoretical capability.
Impact
Low, and I don't want to oversell it. As #2242 notes, Ignition already runs as
root and
as_useris defense-in-depth rather than a security boundary; the onlycaller is
writeAuthKeysFile()ininternal/exec/util/passwd.go, writing SSHauthorized keys during first boot, where root's supplementary groups are
typically just
{0}. This is a correctness fix that brings the privilege dropin line with POSIX practice, and matters more for any future reuse of this code.
Notes
<grp.h>is added for thesetgroups()declaration; the cgo build uses-Werror=implicit-function-declaration, so a missing declaration would befatal rather than silent.
setgroups()fails,set_eids()returns an errorrather than continuing with a partial privilege drop.
setgroups(0, NULL)clears the list rather than installing the target user'sown groups. Installing the real list would mean either an NSS lookup inside
the cloned thread or widening
au_ids_tto carry the group list from Go.Happy to do the latter if you'd prefer it — clearing seemed like the right
minimal fix for the reported issue.
Testing
./test— Success, exit 0 (Fedora 44, Go 1.24, cgo, run as a non-rootuser)
./build ignition— exit 0git diff --check— cleanas_user.cRunning
./testas root instead failsTestTranslateTree/translate_7in theseven
butane/base/*packages, because that test asserts a permission-deniederror which root bypasses. That failure reproduces identically on an unmodified
origin/main, so it is unrelated to this change.No new in-tree test:
internal/as_useris Linux-, cgo- and root-only, has noexisting unit tests, and a regression test for this would have to manipulate the
test process's own credentials. I'm glad to add a root-gated one if you want it.
Fixes #2242.